Blueprint Help Send comments on this topic.
Data Components
See Also

Glossary Item Box

Data Components

The data component is simply a class that derives from a 'ClpNewObject' and has a number of standard functions.    Data components are required for store records, and workspace and method state.  The translator will create a skeletal class definition for each referenced data type.

In the case of state and workspace objects the data component's type is automatically generated using the following schemes;

Method Workspace

MthdType +"MthdWorkspace".  If a method is explicitly defined then MthdType is specified by the definition.  If the method is defined 'in-line' then it's type is constructed from parent circuit type, followed by "_" followed by its instance name.  So if a method named "M" is defined in a circuit of type "MyCct" then its workspace data component's type would be 'MyCct_MMthdWorkspace'.

Call-back Workspace

CbfType +"CbfWorkspace".  If a call-back is explicitly defined then CbfType is specified by the definition.  If the call-back is defined 'in-line' then it's type is constructed from parent circuit type, followed by "_" followed by its instance name.  So if a call-back named "C" is defined in a circuit of type "MyCct" then its workspace data component's type would be 'MyCct_CCbfWorkspace'.

Circuit Workspace

CctType +"CctWorkspace".  So if a circuit has type "MyCct" then its workspace data component's type would be 'MyCct_Workspace'.

Method State

MthdType +"MthdState".  If a method is explicitly defined then MthdType is specified by the definition.  If the method is defined 'in-line' then it's type is constructed from parent circuit type, followed by "_" followed by its instance name.  So if a method named "M" is defined in a circuit of type "MyCct" then its state data component's type would be 'MyCct_MMthdState'.

Store record data uses an explicit naming scheme.  If for example a transient store has data type {Dtype} then a file called "Dtype.hpp" will be generated and will contain the following class definition;

   class Dtype : public ClpNewObject
   {
   public:
  
    /// <summary>
    /// Initializes this data structure
    /// </summary>
    /// <returns>TRUE if successful, FALSE if failed</returns>
      Uns Initialise()
      {
         return TRUE;
      }
      // TODO: Add custom data access functions
   private:
  
      // TODO: Add custom data member variables
   };

Member variables and access functions can then be added.  For example, the following additions shown in red could be made;

   class Dtype : public ClpNewObject
   {
   public:
  
    /// <summary>
    /// Initializes this data structure
    /// </summary>
    /// <returns>TRUE if successful, FALSE if failed</returns>
      Uns Initialise()
      {
         N = 0;
         return TRUE;
      }
      // TODO: Add custom data access functions
      Uns GetN()
      {
         return N;
      }

   private:
  
      // TODO: Add custom data member variables
      Uns   N;
      Float X[100];
   };

Variable Sized Data

Variable sized data is a common requirement and is normally achieved through dynamic allocation and the use of pointers.  Unfortunately pointers are not necessarily valid in asymmetric shared memory environments and so portable applications require alternative approaches that do not involve the use of pointers.  The easiest way to achieve this is to calculate the particular size and then allocate this as a contiguous block.

The pseudo class below contains two arrays, X and Y;

class Dtype
{
   Float f;
   Uns   N;
   Uns   M;
   Int   X[N];
   Int   Y[N][M];
};

The example that follows extends the class to allow N and M to be dynamic.  By convention, there are at least four user functions required;

SetDims([dim1 ,...])

This function stores the variable sized object's dimensions (in this case N and M).  The number of arguments depends on the number of 'size' parameters (in this case 2).

Size([dim1 ,...])

This static function uses the size parameters to calculate the total object size (including the dynamic component).

 

Size()

This function uses the current 'stored' values of the object's size parameters to calculate the current size.

Initialise()

This function initializes the allocated object's data and is automatically called each time the object is constructed.

By convention, the dynamic data is assumed to follow the 'Dtype' class object contiguously and so user access functions are also required that set and return the contents of each dynamic array element.  There are a number of ways that this can be done, and the X_set, Y_set, X_val and Y_val below, are typical examples.

In the example below, the non-dynamic data objects are private and access functions have also been defined for them, but this is not a required convention. 

   class Dtype : public ClpNewObject
   {
   public:
   
      // Store current dimensions
      Uns SetDims( Uns n, Uns m ){
         N = n;
         M = m;
         return TRUE;
      }

      // Calculate object size for n,m
      static Uns Size( Uns n, Uns m ){
         return sizeof( Dtype ) + (m+1)*n*sizeof( Int );
      }

      // Calculate current object size
      Uns Size(){
         return Size( N, M );
      }

      // Perform specific initialization here
      Uns Initialise(){
         return TRUE;
      }

      // Return current value of 'N'
      Uns N_val(){
         return N;
      }

      // Return current value of 'M'
      Uns M_val(){
         return M;
      }

      // Set current value of 'N'
      void N_set( Uns n )
      {
         N = n;
      }

      // Set current value of 'M'
      void M_set( Uns m )
      {
         M = m;
      }

      // Set X array element
      void X_set( Uns idx, Int x ){
         // Assume 'X' follows the 'Dtype' header contiguously
         ((Int*)(this+1))[ idx ] = x;
      }

      // Set Y array element
      void Y_val( Uns idx1, Uns idx2, Int y ){
         // Assume Y follows X contiguously
         Int *Y_start = ((Int*)(this+1))+N;
         Y_start[ idx1*M+idx2 ] = y;
      }

      // Return X array element
      Int X_val( Uns idx ){
         // Assume it follows the 'Dtype' header contiguously
         return ((Int*)(this+1))[ idx ];
      }

      // Return Y array element
      Int Y_val( Uns idx1, Uns idx2 ){
         // Assume Y follows X contiguously
         Int *Y_start = ((Int*)(this+1))+N;
         return Y_start[ idx1*M+idx2 ];
      }

   private:
      // Fixed size components
      Float f;
      Uns N;
      Uns M;
   };

Note that the example above is probably not the most CPU efficient way of accessing large multi-dimensional arrays and a better solution for numerically intensive applications is probably to create a one dimension pointer array that references each 'row' of the Y array.  This array will need to be initialized by the 'writer' and then re-initialized by readers for whom the pointer addresses are not necessarily valid and will need to be recalculated.  By convention, functions that re-calculate and update pointers between reads and writes have entry point name 'link'.  The example 'pseudo' data class now becomes;

class Dtype
{
   Float f;
   Uns   N;
   Uns   M;
   Int   X[N];
   Int   *Yp[N];  // Additional pointer array
   Int   Y[N][M];
};

In this case, the link function becomes;

void Link()
{
   Int *Y_start = ((Int*)(this+1))+N;

   for ( Uns n = 0; n < N; n++ ){
      Yp[n] = Y_start + n*M;
}

This function should be called immediately after the writer's 'Construct' call (see store records, state and workspace), and must also be called before readers access the array.  Once linked, array elements can be referenced using the standard 'Yp[n][m]' syntax.  The static Size function becomes;

// Calculate object size for n,m
static Uns Size( Uns n, Uns m )
{
   return sizeof( Dtype ) + (m+1)*n*sizeof( Int ) + n * sizeof( Int* );
}

CDL Dependence

Note that the derivation from 'ClpNewObject' simply over-rides the C++ new and delete operators and is not actually required for transient and/or arbitrated store data.  It can therefore be removed, in which case the data components become independent of CLIP and can be used in standard library calls that can be re-used outside of CLiP.  This avoids lock in.  State and Workspace objects do require this derivation and so if they are to be re-used in standard libraries then an empty dummy ClpNewObject class should be included in the library code.

WARNING: if building a debug application using MFC and including the CDL headers in the GUI, the MFC headers will override the 'new' using a #define. This can upset the compilation and linking.

Eg.

#ifdef _DEBUG
#define new DEBUG_NEW
#endif 

Removing this predefinition will fix these compilation/linking errors.

 

See Also